import { Monitor } from ".."
import { ExtendedXMLHttpRequest } from "./error-type"
import { MonitorConfig } from "../config"

// import { getLastEvent } from "../utils/getLastEvent"

export class ErrorTracking {
    monitor: InstanceType<typeof Monitor>
    recordScreenId: string = "123"
    constructor(monitor: InstanceType<typeof Monitor>) {
        this.monitor = monitor
        this.init()
    }

    init(): void {
        // JS相关错误、资源错误、异步错误
        this.injectJsError()
        // Promise相关错误
        this.injectPromiseError()
        // XHR请求接口相关错误
        this.injectXHRError()
        this.injectFetchError()
    }

    //#region 1、监控全局未捕获的 js运行错误、语法错误、资源错误、异步错误
    injectJsError(): void {
        window.addEventListener(
            "error",
            (event: Event | ErrorEvent) => {
                // 1. 生成统一ID
                const recordScreenId = this.monitor.generateRecordScreenId()
                this.monitor.recordScreenId = recordScreenId // 将ID暂存到monitor实例

                // 2. 设置错误标记（通知录屏模块需要上报）
                this.monitor.hasError = true

                // 情况1：JS运行时错误（ErrorEvent）
                if ("error" in event) {
                    const errorEvent = event as ErrorEvent
                    console.log("js错误文件位置")

                    const stackLines = errorEvent.error.stack.split("\n") || []
                    // 解析调用位置（通常第三行是实际调用位置）
                    let line = 0
                    let column = 0
                    let fileName = ""
                    if (stackLines.length >= 3) {
                        const match = stackLines[1].match(/(http?:\/\/[^\s]+?):(\d+):(\d+)/) || stackLines[2].match(/(http?:\/\/[^\s]+?):(\d+):(\d+)/)
                        if (match) {
                            fileName = match[1]
                            line = parseInt(match[2])
                            column = parseInt(match[3])
                        }
                    }

                    this.monitor.transport?.sendErrorInfo({
                        type: "error",
                        errorType: "jsError",
                        message: errorEvent.message,
                        fileName,
                        recordScreenId,
                        line,
                        column,
                        stack: this.getStackInfo(errorEvent.error?.stack),
                        time: Date.now(),
                        pageUrl: window.location.href,
                        behavior: this.monitor.behavior?.getStack()
                    })
                }

                // 情况2：资源加载错误（Event）
                if (event.target) {
                    const target = event.target as HTMLElement
                    const resourceUrl =
                        "src" in target ? (target as HTMLScriptElement | HTMLImageElement).src : "href" in target ? (target as HTMLLinkElement).href : null
                    if (resourceUrl) {
                        // 入栈
                        this.monitor.behavior?.push({
                            category: "Resource_Error",
                            type: "resource",
                            status: "error",
                            time: Date.now(),
                            message: `${target.localName}资源加载失败`,
                            name: target.localName
                        })

                        this.monitor.transport?.sendErrorInfo({
                            type: "error",
                            errorType: "resourceError",
                            message: `${target.localName}资源加载失败`,
                            fileName: resourceUrl,
                            recordScreenId,
                            tagName: target.localName,
                            stack: `Resource: ${resourceUrl}=>Element: ${target.outerHTML.slice(0, 100)}`,
                            time: Date.now(),
                            pageUrl: window.location.href,
                            behavior: this.monitor.behavior?.getStack()
                        })

                        // 改为了依赖hasError标记的错误上报
                        // 触发录屏上报（不再需要等待，由录屏模块的检查点机制处理）
                    }
                }
            },
            true
        )
    }
    //#endregion

    //#region 2、监控全局未处理的 Promise错误
    injectPromiseError(): void {
        // unhandledrejection 是一个事件，当一个 JavaScript Promise 被拒绝（rejected）且没有通过 .catch() 或其他方式处理这个拒绝时，该事件会被触发
        window.addEventListener("unhandledrejection", event => {
            // 1. 生成统一ID
            const recordScreenId = this.monitor.generateRecordScreenId()
            this.monitor.recordScreenId = recordScreenId // 将ID暂存到monitor实例

            // 2. 设置错误标记（通知录屏模块需要上报）
            this.monitor.hasError = true

            // 3. 提取错误信息
            let message: any, fileName: any, line: number | undefined, column: number | undefined, stack: string | undefined

            if (typeof event.reason === "string") {
                // 说明错误是一个字符串
                message = event.reason
            } else if (typeof event.reason === "object") {
                console.log(event)

                // 说明错误是一个对象
                message = event.reason.message
                // at http://localhost:3000/static/js/bundle.js:208292:19
                if (event.reason.stack) {
                    const matchResult = event.reason.stack.match(/at\s+(.+):(\d+):(\d+)/)
                    fileName = matchResult[1]
                    line = parseInt(matchResult[2], 10) // 10表示10进制
                    column = parseInt(matchResult[3], 10) // 10表示10进制
                }

                stack = this.getStackInfo(event.reason.stack)
            }

            // 上报数据
            this.monitor.transport?.sendErrorInfo({
                type: "error",
                errorType: "promiseError",
                message,
                fileName,
                recordScreenId,
                line,
                column,
                stack,
                time: Date.now(),
                pageUrl: window.location.href,
                behavior: this.monitor.behavior?.getStack()
            })

            // 改为了依赖hasError标记的错误上报
            // 触发录屏上报（不再需要等待，由录屏模块的检查点机制处理）

            // 上报录屏数据
            // this.monitor.recordScreen?.uploadRecordSegment(this.recordScreenId, this.time, window.location.href)
        })
    }
    //#endregion

    //#region 3、监控未处理的XHR报错
    injectXHRError(): void {
        // 判断浏览器是否支持xhr（兼容性检查）
        if (!("XMLHttpRequest" in window)) {
            return
        }
        // 这里this指向monitor实例
        const monitorThis = this.monitor
        const XMLHttpRequest = window.XMLHttpRequest
        // 缓存老的XHR open 方法
        const oldOpen = XMLHttpRequest.prototype.open
        // const sendUrl = this.monitor.options.reportErrorInfoUrl
        // 重写 XHR open 方法
        XMLHttpRequest.prototype.open = function (this: ExtendedXMLHttpRequest, method: string, url: string | URL, async: boolean = true) {
            // 标准化请求的 URL（兼容 string 和 URL 对象）
            const requestUrl = typeof url === "string" ? url : url.href // url.href返回完整的 URL 字符串

            if (
                !requestUrl.includes(MonitorConfig.performanceUrl) ||
                !requestUrl.includes(MonitorConfig.errorUrl) ||
                !requestUrl.includes(MonitorConfig.blankScreenUrl) ||
                !requestUrl.includes(MonitorConfig.recordScreenUrl)
            ) {
                this.logData = { method, url, async }
            }
            // if (!requestUrl.includes(sendUrl)) {
            //     // 给XHR属性添加logData对象
            //     this.logData = { method, url, async }
            // }
            // 使用剩余参数代替 arguments
            return oldOpen.call(this, method, url, async)
        }

        // 缓存老的XHR send 方法
        const oldSend = XMLHttpRequest.prototype.send
        // 记录开始时间
        let startTime: number
        // 重写 XHR send 方法
        XMLHttpRequest.prototype.send = function (this: ExtendedXMLHttpRequest, body) {
            // 拦截
            if (this.logData) {
                startTime = Date.now()
                // 捕获调用栈
                const stackTrace = new Error().stack
                const stackLines = stackTrace?.split("\n") || []
                // 解析调用位置（通常第三行是实际调用位置）
                let line = 0
                let column = 0
                let fileName = ""
                if (stackLines.length >= 3) {
                    const match = stackLines[2].match(/(http?:\/\/[^\s]+?):(\d+):(\d+)/)
                    if (match) {
                        fileName = match[1]
                        line = parseInt(match[2])
                        column = parseInt(match[3])
                    }
                }
                // 回调函数
                const handler = (type: string) => {
                    return () => {
                        // 1. 生成统一ID
                        const recordScreenId = monitorThis.generateRecordScreenId()
                        monitorThis.recordScreenId = recordScreenId // 将ID暂存到monitor实例

                        // 2. 设置错误标记（通知录屏模块需要上报）
                        monitorThis.hasError = true

                        const duration = Date.now() - startTime // 持续时间
                        const status = this.status // 200 、500
                        const statusText = this.statusText // Ok 、 Server Error
                        // 上报数据
                        monitorThis.transport?.sendErrorInfo({
                            type,
                            errorType: "xhr-error", // load 、error 、abort
                            message: "Maybe XHR Request Error",
                            method: this.logData?.method,
                            fileName,
                            recordScreenId,
                            line,
                            column,
                            requestUrl: this.logData?.url?.toString(), // 请求url
                            duration, // 持续时间
                            status: status + "-" + statusText,
                            time: Date.now(),
                            pageUrl: window.location.href,
                            response: this.response ? JSON.stringify(this.response) : "", // 响应体
                            behavior: monitorThis.behavior?.getStack()
                        })
                        // 通过检查点和错误标记上报
                    }
                }

                // 请求过程 :执行该回调
                // this.addEventListener("load", handler("load"), false)
                // 请求失败 :执行该回调
                this.addEventListener("error", handler("error"), { once: true }) // 使用一次性监听，每次都会调用该函数
                // 请求放弃（也就是中断请求过程）:执行该回调
                // this.addEventListener("abort", handler("abort"), false)
            }
            // 使用剩余参数代替 arguments
            return oldSend.call(this, body)
        }
    }
    //#endregion

    //#region 4、监控未处理的fetch报错
    injectFetchError(): void {
        if (!("fetch" in window)) return

        const monitorThis = this.monitor

        const sendUrl = Object.values(MonitorConfig)

        const originalFetch = window.fetch

        window.fetch = function (input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
            // fetch请求url为requestUrl
            const requestUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url

            // 排除上报请求本身的监控
            // if (requestUrl.includes(sendUrl as string)) {
            //     return originalFetch(input, init)
            // }

            for (const item of sendUrl) {
                if (requestUrl.includes(item)) {
                    return originalFetch(input, init)
                }
            }

            // 捕获调用栈
            const stackTrace = new Error().stack
            const stackLines = stackTrace?.split("\n") || []

            // 解析调用位置（通常第三行是实际调用位置）
            let line = 0
            let column = 0
            let fileName = ""
            if (stackLines.length >= 3) {
                const match = stackLines[2].match(/(http?:\/\/[^\s]+?):(\d+):(\d+)/)
                if (match) {
                    fileName = match[1]
                    line = parseInt(match[2])
                    column = parseInt(match[3])
                }
            }

            const method = (init?.method || "GET").toUpperCase()
            const startTime = Date.now()

            return originalFetch(input, init)
                .then(response => {
                    // 处理HTTP错误状态（4xx/5xx）
                    if (!response.ok) {
                        // 1. 生成统一ID
                        const recordScreenId = monitorThis.generateRecordScreenId()
                        monitorThis.recordScreenId = recordScreenId // 将ID暂存到monitor实例

                        // 2. 设置错误标记（通知录屏模块需要上报）
                        monitorThis.hasError = true
                        const duration = Date.now() - startTime
                        response
                            .clone()
                            .text()
                            .then(responseText => {
                                monitorThis.transport?.sendErrorInfo({
                                    type: "error",
                                    errorType: "fetch-error",
                                    message: "Maybe Fetch request Error",
                                    method,
                                    fileName,
                                    recordScreenId,
                                    line,
                                    column,
                                    requestUrl,
                                    duration,
                                    status: `${response.status}-${response.statusText}`,
                                    time: Date.now(),
                                    pageUrl: window.location.href,
                                    response: responseText.slice(0, 1000),
                                    behavior: monitorThis.behavior?.getStack()
                                })
                            })
                    }
                    return response
                })
                .catch(error => {
                    // 处理网络层错误（跨域/断网等）
                    const duration = Date.now() - startTime
                    // 1. 生成统一ID
                    const recordScreenId = monitorThis.generateRecordScreenId()
                    monitorThis.recordScreenId = recordScreenId // 将ID暂存到monitor实例

                    // 2. 设置错误标记（通知录屏模块需要上报）
                    monitorThis.hasError = true
                    monitorThis.transport?.sendErrorInfo({
                        type: "error",
                        errorType: "fetch-network-error",
                        message: "Maybe Network Error",
                        method,
                        fileName,
                        recordScreenId,
                        line,
                        column,
                        requestUrl,
                        duration,
                        status: "0-Fetch Failed",
                        time: Date.now(),
                        pageUrl: window.location.href,
                        response: String(error).slice(0, 1000),
                        behavior: monitorThis.behavior?.getStack()
                    })

                    throw error
                })
        }
    }

    //#region 获取栈信息
    getStackInfo(stack: string) {
        // slice不改变原数组，返回裁切后的数组，比如list = [1,2,3,4] ,list.slice(1,3)  => [2,3]
        return stack
            .split("\n")
            .slice(1)
            .map(item => item.replace(/^\s+at\s+/g, "")) //  /g：表示全局匹配 \s+：表示匹配一个或多个空白字符
            .join("=>")
    }
    //#endregion
}
